/**
 * 
 */
package co.fitstart.entity.utils;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;

/**
 * @author Downpour
 * @author lute
 */
public abstract class EnumUtils {
    
    /**
     * Scans all the {@code enum}s under the base package
     * 
     * @param basePackage  the package to scan {@code enum}
     * @return             a Map whose key is {@code enum}'s alias name and
     *                     value is the array of the {@code enum}'s values
     */
    @SuppressWarnings("rawtypes")
    public static Map<String, Enum[]> scan(String basePackage) {
        
        Map<String, Enum[]> enums = new HashMap<String, Enum[]>();
        
        // first find out all the enum classes according to the entity packages
        ClasspathScanner<Enum<?>> scanner = new ClasspathScanner<Enum<?>>();
        scanner.findImplementations(Enum.class, new String[]{basePackage});
        
        for(Class<?> clazz : scanner.getClasses()) {
            
            Alias alias = clazz.getAnnotation(Alias.class);
            String key = alias != null ? alias.value() : clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1);
            
            try {
                enums.put(key, (Enum[]) clazz.getMethod("values").invoke(clazz));
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        
        return enums;
    }

    /**
     * Describe Enumeration to Map. It is guaranteed enumeration's name and ordinal
     * is included but class and declaringClass property is excluded
     * 
     * @param enumeration
     * @return
     */
    public static Map<String, Object> describe(Enum<?> enumeration) {
        try {
            
            Map<String, Object> properties = PropertyUtils.describe(enumeration);
            
            // Remove irrelevant class property
            if (properties.containsKey("class")) {
                properties.remove("class");
            }
            if (properties.containsKey("declaringClass")) {
                properties.remove("declaringClass");
            }
            
            // Add name and ordinal properties if necessary
            if (!properties.containsKey("name")) {
                properties.put("name", enumeration.name());
            }
            if (!properties.containsKey("ordinal")) {
                properties.put("ordinal", enumeration.ordinal());
            }
            
            return properties;
        } catch (NoSuchMethodException nsme) {
            nsme.printStackTrace();
        } catch (InvocationTargetException ite) {
            ite.printStackTrace();
        } catch (IllegalAccessException iae) {
            iae.printStackTrace();
        }
        return null;
    }

    /**
     * Describe Enumeration type to a {@link List} of {@link Map}.
     * Use {@link #describe(Enum)} as the inner implementation.
     * 
     * @param clazz
     * @return
     */
    public static List<Map<String, Object>> describeEnum(Class<? extends Enum<?>> clazz) {
        Enum<?>[] enums = clazz.getEnumConstants();
        List<Map<String, Object>> properties = new ArrayList<Map<String, Object>>();
        for (Enum<?> enumeration : enums) {
            properties.add(describe(enumeration));
        }
        return properties;
    }

}
